﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MyBasicLib.Collections.Generic
{
    public abstract class ExpirationCache<TKey, TValue> : ICache<TKey, TValue>
    {
        private readonly Dictionary<TKey, CacheItem<TKey, TValue>> _cache;
        private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
        private readonly System.Timers.Timer _timer;
        public TimeSpan TimeSpanDelay { get; set; }
        public ExpirationCache()
            : this(TimeSpan.FromMinutes(30))
        {
        }
        public ExpirationCache(TimeSpan timeSpanDelay)
            : this(null, timeSpanDelay)
        {
        }

        public ExpirationCache(IEqualityComparer<TKey> comparer, TimeSpan timeSpanDelay)
        {
            TimeSpanDelay = timeSpanDelay;
            _cache = new Dictionary<TKey, CacheItem<TKey, TValue>>(comparer);
            _timer = new System.Timers.Timer();
            _timer.Interval = 10 * 1000;
            _timer.Elapsed += _timer_Elapsed;
            _timer.Start();
        }
        public int Count
        {
            get
            {
                _rwLock.EnterReadLock();
                try
                {
                    return _cache.Count;
                }
                finally
                {
                    _rwLock.ExitReadLock();
                }
            }
        }

        public TValue FetchOrCreateItem(TKey key, Func<TKey, TValue> creator)
        {
            // first, see if the item already exists in the cache
            _rwLock.EnterReadLock();
            try
            {
                CacheItem<TKey, TValue> item;
                if (_cache.TryGetValue(key, out item))
                {
                    SetLastLastAccessTime(item);
                    return item.Value;
                }
            }
            finally
            {
                _rwLock.ExitReadLock();
            }
            if (creator == null) return default(TValue);

            // insert the new item into the cache

            _rwLock.EnterWriteLock();
            try
            {
                CacheItem<TKey, TValue> item;
                if (_cache.TryGetValue(key, out item))
                {
                    SetLastLastAccessTime(item);
                    return item.Value;
                }
                if (creator == null) return default(TValue);
                item = new CacheItem<TKey, TValue>() { LastAccess = DateTime.Now };
                item.Key = key;
                item.Value = creator(key);
                _cache[key] = item;
                return item.Value;
            }
            finally
            {
                _rwLock.ExitWriteLock();
            }
        }

        public void InvalidateItem(TKey key)
        {
            _rwLock.EnterWriteLock();
            try
            {
                _cache.Remove(key);
            }
            finally
            {
                _rwLock.ExitWriteLock();
            }
        }

        protected virtual void SetLastLastAccessTime(CacheItem<TKey, TValue> item)
        {
            item.LastAccess = DateTime.Now;
        }

        void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            CacheItem<TKey, TValue>[] dic;
            _rwLock.EnterWriteLock(); 
            try
            {
                 dic = new CacheItem<TKey, TValue>[_cache.Count];
                _cache.Values.CopyTo(dic, 0);
            }
            finally
            {
                _rwLock.ExitWriteLock();

            }

            for (var i = 0; i < dic.Length; i++)
            {
                if (DateTime.Now - dic[i].LastAccess > TimeSpanDelay)
                {

                    _rwLock.EnterWriteLock();
                    try
                    {
                        _cache.Remove(dic[i].Key);
                    }
                    finally
                    {
                        _rwLock.ExitWriteLock();
                    }

                }
            }
        }

    }
}
